﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Compilex.BaseLibrary;
using Compilex.Syntatic.Declarations;
using Compilex.Syntatic.Descriptors;
using Compilex.Syntatic;

namespace Compilex.CodeGeneration
{
    public class CodeGenerator
    {
        internal CallStack CallStack { get; set; }
        internal Labels Labels { get; set; }
        internal CommonOperations Ops { get; set; }
        internal LibraryFunctions LibraryFunctions { get; set; }

        internal StringBuilder CodeBuilder { get; set; }
        internal StringBuilder GlobalDataBuilder { get; set; }
        internal StringBuilder TailDataBuilder { get; set; }
        
        public SyntaticContext Context { get; set; }

        private string GlobalData
        {
            get { return GlobalDataBuilder.ToString(); }
        }

        private string Code
        {
            get { return CodeBuilder.ToString(); }
        }

        private string TailData
        {
            get { return TailDataBuilder.ToString(); }
        }

        public CodeGenerator(SyntaticContext context)
        {
            this.Context = context;
            CallStack = new CallStack(this);
            GlobalDataBuilder = new StringBuilder();
            TailDataBuilder = new StringBuilder();
            Labels = new Labels(this);
            CodeBuilder = new StringBuilder();
            Ops = new CommonOperations(this);
            LibraryFunctions = new LibraryFunctions(this);

            if (Context.ScopeManager.CurrentScope.ParentScope == null)
            {
                InitializeProgram();
            }

        }

        private void InitializeProgram()
        {
                CodeBuilder.AppendLine("@ /0");
                CodeBuilder.AppendLine("LV END");
                Ops.StoreTo(CallStack.StackBase);
                Ops.JumpTo(Labels.GetFunctionLabel("principex"));

                TailDataBuilder.AppendLine("END HM /0");
                TailDataBuilder.AppendLine("INSTMOVE MM /0");
                TailDataBuilder.AppendLine("INSTLOAD LD /0");
                TailDataBuilder.AppendLine("SP K " + CallStack.StackBase);
                TailDataBuilder.AppendLine("BP K " + CallStack.StackBase);
        }

        public string GetCode()
        {
            return CodeBuilder.ToString()+GlobalDataBuilder.ToString()+TailDataBuilder.ToString();
        }

        public void AddVariable(Symbol symbol)
        {
                AddGlobalVariable(symbol.Identifier, ((VariableDescriptor)symbol.Descriptor).Type);
        }

        internal string AddGlobalVariable(string name, SymbolType type)
        {
            GlobalDataBuilder.AppendLine(Labels.GetGlobalVariableLabel(name) + " K /0");
            return Labels.GetGlobalVariableLabel(name);
            // todo estrutura
        }

        internal void AddGlobalArray(Symbol symbol)
        {
            VariableDescriptor descriptor = (VariableDescriptor)symbol.Descriptor;
            GlobalDataBuilder.AppendLine(Labels.GetGlobalVariableLabel(symbol.Identifier) + " $ ="
                + descriptor.SizeBytes);
        }

        // aqui é o finex funcex
        public void EndFunction(FunctionDescriptor fDescriptor, string functionName)
        {
            StringBuilder inicio = new StringBuilder();
            Function function = new Function(this, inicio);
            function.Prologue(functionName, fDescriptor.LocalVariablesStackSizeBytes);
            
            CodeBuilder.Insert(0, inicio);

            // faço append no fim do código de organizar pilha e jump para retorno
            int stackSize = 0;
            if (!fDescriptor.IsVoid)
            {
                stackSize += fDescriptor.ReturnType.SizeBytes;
            }
            stackSize += fDescriptor.ReturnAddressSize  + 2 + fDescriptor.LocalVariablesStackSizeBytes + fDescriptor.ParamsStackSize;

            // HACK: não sei se eh apenas para a principex que precisa isso
            //if (functionName == "principex")
            //    stackSize -= 2;

            function = new Function(this, CodeBuilder);
            function.Epilogue(stackSize, fDescriptor.LocalVariablesStackSizeBytes, functionName);          
        }

        public void JumpToEpilogue(string functionName)
        {
            Ops.Comment("função retornou; pula para o epílogo");
            Ops.JumpTo(Labels.GetEpilogueLabel(functionName));
        }

        public void MergeWithChildBlock(CodeGenerator child)
        {
            GlobalDataBuilder.Append(child.GlobalData);
            CodeBuilder.Append(child.Code);
            TailDataBuilder.Append(child.TailData);
        }

        public void PushReturn(FunctionDescriptor fDescriptor, string functionName)
        {
            new Function(this, CodeBuilder).PushReturn();
        }

        public void AssignValue(Symbol symbol)
        {

            if (SymbolTableHelper.IsGlobalSymbol(symbol, Context))
            {
                Ops.Move(Labels.GetGlobalVariableLabel(symbol.Identifier),
                    Labels.GetResultLabel());
            }
            else
            {
                VariableDescriptor descriptor = (VariableDescriptor)symbol.Descriptor;
                Function function = new Function(this, CodeBuilder);
                if(descriptor.IsParam)
                {
                    function.StoreToArgument(Labels.GetResultLabel(), descriptor.StackOffset);
                }
                else
                {
                    function.StoreToLocalVariable(Labels.GetResultLabel(), descriptor.StackOffset);
                }
            }
        }

        public void AssignValueArrayElement(Symbol symbol, int offset)
        {
            VariableDescriptor descriptor = (VariableDescriptor)symbol.Descriptor;
            if (SymbolTableHelper.IsGlobalSymbol(symbol, Context))
            {
                Ops.MoveToPointer(Labels.GetGlobalVariableLabel(symbol.Identifier), offset * descriptor.Type.SizeBytes,
                    Labels.GetResultLabel());
            }
            else
            {
                Function function = new Function(this, CodeBuilder);
                if (descriptor.IsParam)
                {
                    function.StoreToArgument(Labels.GetResultLabel(), descriptor.StackOffset - offset * descriptor.Type.SizeBytes);
                }
                else
                {
                    function.StoreToLocalVariable(Labels.GetResultLabel(), descriptor.StackOffset - offset * descriptor.Type.SizeBytes);
                }
            }
        }

        public string AddArithmeticOperation(ArithmeticOperand first, ArithmeticOperand second, string _operator)
        {
            Ops.Comment("carrega primeiro operando");
            LoadOperand(first);

            Ops.Comment("salva primeiro operando");
            string firstOperand = Ops.StoreAccToTemp();

            Ops.Comment("carrega segundo operando");
            LoadOperand(second);

            Ops.Comment("salva segundo operando");
            string secondOperand = Ops.StoreAccToTemp();

            Ops.Comment("faz operação");
            Ops.Load(firstOperand);
            CodeBuilder.AppendLine(_operator + " " + secondOperand);

            Ops.Comment("retorna");
            return Ops.StoreAccToTemp();
        }


        public string AddArithmeticOperation(ArithmeticOperand operand)
        {
            LoadOperand(operand);
            return Ops.StoreAccToTemp();
        }


        private void LoadOperand(ArithmeticOperand operand)
        {
            if (operand.IsSymbol)
            {
                if (SymbolTableHelper.IsGlobalSymbol(operand.Symbol, Context))
                {
                    Ops.Load(Labels.GetGlobalVariableLabel(operand.Symbol.Identifier));
                }
                else
                {
                    VariableDescriptor descriptor = (VariableDescriptor)operand.Symbol.Descriptor;
                    Function function = new Function(this, CodeBuilder);
                    if (descriptor.IsParam)
                    {
                        function.LoadArgument(descriptor.StackOffset);
                    }
                    else
                    {
                        function.LoadLocalVariable(descriptor.StackOffset);
                    }
                }
            }
            else if (operand.IsArrayElement)
            {
                VariableDescriptor descriptor = (VariableDescriptor)operand.Array.Descriptor;
                if (SymbolTableHelper.IsGlobalSymbol(operand.Array, Context))
                {
                    Ops.LoadFromPointer(Labels.GetGlobalVariableLabel(operand.Symbol.Identifier),
                        operand.ArrayOffset * descriptor.Type.SizeBytes );
                }
                else
                {
                    // precisa somar na pilha, pois StackOffset aponta para o topo do vetor
                    Function function = new Function(this, CodeBuilder);
                    if (descriptor.IsParam)
                    {
                        function.LoadArgument(descriptor.StackOffset + operand.ArrayOffset * descriptor.Type.SizeBytes);
                    }
                    else
                    {
                        function.LoadLocalVariable(descriptor.StackOffset - operand.ArrayOffset * descriptor.Type.SizeBytes);
                    }
                }
            }
            else if (operand.IsConstant)
            {
                Ops.Load(operand.Value);
            }
            else if (operand.IsTemporary)
            {
                Ops.Load(operand.TempLabel);
            }
            else
            {
                throw new InternalSyntaticException("Operador não é símbolo nem constante!?");
            }

        }

        public string AddBooleanOperation(BooleanOperand first, BooleanOperand second, string _operator)
        {
            if (first.IsSymbol || second.IsSymbol)
                throw new InternalSyntaticException("BooleanOperand é símbolo");

            if (_operator == "&&" || _operator == "||")
            {
                return AddBooleanAndOrOperation(first, second, _operator);
            }
            else
            {
                return AddBooleanComparisonOperator(first, second, _operator);
            }
        }

        private string AddBooleanComparisonOperator(BooleanOperand first, BooleanOperand second, string _operator)
        {
            ArithmeticOperand afirst = ArithmeticOperandFromBooleanOperand(first);
            ArithmeticOperand asecond = ArithmeticOperandFromBooleanOperand(second);

            string resultLabel = null;
            if (_operator == "<=" || _operator == ">")
            {
                resultLabel = AddArithmeticOperation(asecond, afirst, "-");
            } else
            {
                resultLabel = AddArithmeticOperation(afirst, asecond, "-");
            }
            string endLabel = Labels.GetAssignLabel();
            if (_operator == "==" || _operator == "!=")
            {
                string equalLabel = Ops.JumpZero();
                // se continuou, é porque não pulou: são diferentes
                Ops.StoreTo(_operator == "==" ? 0 : 1, resultLabel);
                Ops.JumpTo(endLabel);
                // se esta aqui, são iguais
                Ops.Nop(equalLabel);
                Ops.StoreTo(_operator == "==" ? 1 : 0, resultLabel);
            }
            else
            {
                if (_operator == "<" || _operator == ">=")
                {
                    string lesserThan = Ops.JumpNegative();
                    // se continuou, é porque não pulou: first < second
                    Ops.StoreTo(_operator == "<" ? 0 : 1, resultLabel);
                    Ops.JumpTo(endLabel);
                    // se esta aqui, first >= second
                    Ops.Nop(lesserThan);
                    Ops.StoreTo(_operator == "<" ? 1 : 0, resultLabel);
                }
                if (_operator == "<=" || _operator == ">")
                {
                    // para esses operadores, fazemos a 
                    string lesserThan = Ops.JumpNegative();
                    // se continuou, é porque não pulou: first <= second
                    Ops.StoreTo(_operator == "<=" ? 1 : 0, resultLabel);
                    Ops.JumpTo(endLabel);
                    // se esta aqui, first > second
                    Ops.Nop(lesserThan);
                    Ops.StoreTo(_operator == ">" ? 1 : 0, resultLabel);
                }
            }
            // fim
            Ops.Nop(endLabel);
            return resultLabel;
        }

        private string AddBooleanAndOrOperation(BooleanOperand first, BooleanOperand second, string _operator)
        {
            ArithmeticOperand afirst = ArithmeticOperandFromBooleanOperand(first);
            ArithmeticOperand asecond = ArithmeticOperandFromBooleanOperand(second);

            string aOperator = _operator == "&&" ? "*" : "+";
            string resultLabel = AddArithmeticOperation(afirst, asecond, aOperator);

            Ops.Sub(resultLabel, 1);
            string continueLabel = Ops.JumpNegative();
            Ops.StoreTo(1, resultLabel);

            Ops.Nop(continueLabel);
            return resultLabel;
        }

        private ArithmeticOperand ArithmeticOperandFromBooleanOperand(BooleanOperand boperand)
        {
            ArithmeticOperand aoperand = new ArithmeticOperand();
            if (boperand.IsConstant)
            {
                aoperand.IsConstant = true;
                aoperand.Value = boperand.Value ? 1 : 0;
            }
            else
            {
                aoperand.IsSymbol = boperand.IsSymbol;
                aoperand.Symbol = boperand.Symbol;
                aoperand.IsTemporary = boperand.IsTemporary;
                aoperand.TempLabel = boperand.TempLabel;
            }
            return aoperand;
        }


        public void PushSimpleArgument()
        {
            new CallStack(this).PushWord(Labels.GetResultLabel());
        }

        public void PushLiteralArgument(string literal)
        {
            //new CallStack(this).PushWord(Labels.GetResultLabel());
        }

        // chamada ao receber o parenteses de fim do call, sendo função como expressao aritmetica
        public string EndFunctionCall(FunctionDescriptor fDescriptor, string functionName )
        {
            string returnLabel = Labels.GetAssignLabel();

            int returnSize = 0;
            if (!fDescriptor.IsVoid)
            {
                // reserva espaço para retorno
                Ops.Comment("Chamando Função " + functionName);
                Ops.Comment("Espaço para Retorno");
                CallStack.IncrementStack(fDescriptor.ReturnType.SizeBytes);
                returnSize = fDescriptor.ReturnType.SizeBytes;
            }
         
            // guarda o endereço de retorno
            Ops.Comment("coloca endereço de retorno na pilha");
            CallStack.PushAddress(returnLabel, fDescriptor.ReturnAddressSize);

            // pula para a função
            Ops.JumpTo(Labels.GetFunctionLabel(functionName));
            Ops.Nop(returnLabel);

            // aki precisa criar um result label
            string resultLabel = Labels.IncrementResultsLabel();

            Ops.LoadFromPointer("SP", -(fDescriptor.ParamsStackSize + returnSize));
           
            // colocou o resultando que a função retornou no temp result
            CodeBuilder.AppendLine("MM " + Labels.GetResultLabel());

            return Labels.GetResultLabel();
        }


        //public void Epilogue(int stackSize, int localVariablesSize, string functionName)
        //{
        //    new Function(this, CodeBuilder).Epilogue(stackSize, localVariablesSize, functionName);
        //}


        public string AddBooleanOperation(BooleanOperand booleanOperand)
        {
            if (booleanOperand.IsConstant)
            {
                return AddArithmeticOperation(ArithmeticOperandFromBooleanOperand(booleanOperand));
            }
            return booleanOperand.TempLabel;
        }

        public string AddBooleanOperation(BooleanOperand operand, string unaryOperation)
        {
            if (unaryOperation == "!")
            {
                if (operand.IsConstant)
                {
                    Labels.IncrementResultsLabel();
                    Ops.StoreTo(operand.Value ? 0 : 1, Labels.GetResultLabel());
                    return Labels.GetResultLabel();
                }
                else
                {
                    Labels.IncrementResultsLabel();

                    Ops.Load(operand.TempLabel);
                    string falsexLabel = Labels.GetAssignLabel();
                    Ops.JumpZero(falsexLabel);
                    // operand == true;
                    Ops.StoreTo(0, Labels.GetResultLabel());
                    
                    string endLabel = Labels.GetAssignLabel();
                    Ops.JumpTo(endLabel);
                    // operand == false
                    Ops.StoreTo(1, Labels.GetResultLabel());
                    // fim
                    Ops.Load(0, endLabel);

                    return Labels.GetResultLabel();
                }
            }
            else
            {
                throw new InternalSyntaticException("Operador unário não conhecido: " + unaryOperation);
            }
        }

        private string ifConditionLabel = null;
        private string ifJumpFalseLabel = null;
        private string ifExitLabel = null;
        public void StartIf()
        {
            ifConditionLabel = Labels.GetResultLabel();
            ifExitLabel = Labels.GetAssignLabel();
            ifJumpFalseLabel = ifExitLabel; // se houver else, a função Else irá mudar ifExitLabel
            Ops.Load(ifConditionLabel);
            Ops.JumpZero(ifJumpFalseLabel);
        }

        public void Else()
        {
            // criamos um novo exitLabel e damos jump para ele
            // se a condição do if for true, ele cairá nesse jump. caso contrário,
            // ele terá feito o jump para ifJumpFalseLabel
            ifExitLabel = Labels.GetAssignLabel();
            Ops.JumpTo(ifExitLabel);
            // escrevemos aqui o ifJumpFalseLabel
            Ops.Nop(ifJumpFalseLabel);
        }

        public void EndIf()
        {
            Ops.Nop(ifExitLabel);
        }

        private string whileConditionComputationLabel = null;
        private string whileExitLabel = null;

        public void StartWhile()
        {
            whileConditionComputationLabel = Labels.GetAssignLabel();
            Ops.Nop(whileConditionComputationLabel);
        }

        public void AfterWhileExpression()
        {
            string conditionLabel = Labels.GetResultLabel();
            Ops.Comment("condição do enquantex");
            Ops.Load(conditionLabel);
            whileExitLabel = Labels.GetAssignLabel();
            Ops.Comment("pula para exit label");
            Ops.JumpZero(whileExitLabel);
        }

        public void EndWhile()
        {
            Ops.Comment("volta para o começo do loop");
            Ops.JumpTo(whileConditionComputationLabel);
            Ops.Nop(whileExitLabel);
        }

        public void DoLibraryFunctionCall(string functionName)
        {
            LibraryFunctions.DoCall(functionName);
        }


        public void PushLibraryFunctionArgument()
        {
            LibraryFunctions.PushLibraryFunctionArgument(Labels.GetResultLabel());
        }

        internal string GetLatestTempLabel()
        {
            return Labels.GetResultLabel();
        }

        
    }
}
